#pragma once
#include "strv.h"

using namespace std;

extern bool g_bExceptionMade;
extern class CScript * g_pScript;
extern class CProject * g_pProject;
extern char g_szFileBuffer[2048];
extern char g_szOutputBuffer[512];
extern char g_szTempString[4096];
extern char g_szErrorString[2048];
extern string g_sTempString;
extern class CParser Parser;
extern int g_nErrorStackCount;
extern DWORD g_dwVersion;
//extern bool g_bExit;

#ifdef _DEBUG
//#define DEBUGIT
#else
//#define DEBUGIT
#endif

#ifdef DEBUGIT
extern char DEBUG_STRING[4096];

#define DEBUG_TRACE printf
#define _STRING(a) #a
#define STRING(s) _STRING(s)
#else
#define DEBUG_TRACE __noop
#endif

#define TODO(s) __pragma(message("TODO: "s" ("__FUNCSIG__")"))\


typedef void*	call;

// Use to throw exceptions esp. from code errors with format
#define THROW(a,...) do{sprintf(g_szErrorString, a, __VA_ARGS__);throw(g_szErrorString);}while(0)

#ifdef _DEBUG
#define ASSERT _ASSERT
#else
#define ASSERT __noop
#endif

// This should have its own wrapper function
#define GetWorkingDir _getcwd

void			ProcessCommand(char *cmd);

// Wrapper functions for STD and API functions - for ease of future cross-compatibility
inline void				*	AlignAddress(size_t pAddress, size_t alignment=4);
__int64						JoinInt32(int high, int low);
__int64						GetTime();
__int64						GetFileModifiedTime(FILE *hFile);
__int64						GetFileModifiedTime(const char *szFilePath);
__int64						FiletimeToInt64(const FILETIME &ft);
bool						DoesDirExist(const char *szDirPath);
void						GetFileDirectory(const char *szFilePath, char *szDirectory, int nSize=0);
extern inline FILE		*	OpenExecutable(const char * szCmd, const char * szMode="r");
extern inline FILE		*	OpenFile(const char *szFilePath, const char *szOpenMode);
extern inline int			CloseExecutable(FILE *hFile);
extern inline void			CloseFile(FILE *hFile);
extern inline DWORD			FileGetSize(FILE *hFile);
extern inline int			FileGetCursorPos(FILE *hFile);
extern inline bool			IsEndOfFile(FILE *hFile);
char					*	GetConfigLine(FILE *hFile);
int							SeekFile(FILE *hFile, int offset, int origin);
bool						WriteStringToFile(FILE *hFile, const char *str);
size_t						WriteBytesToFile(FILE *hFile, const void *buff, int len);
bool						ReadStringFromFile(FILE *hFile, char *str, int len);
size_t						ReadBytesFromFile(FILE *hFile, void *buff, int len);

template<typename T> inline T ReadFromFile(FILE *hFile);
template<typename T> inline size_t WriteToFile(FILE *hFile, T buff);
template<typename T> inline const char * tostr(const T& v);
template<typename T> inline string tostring(const T& v);
template<typename T> inline const char * tohexstr(const T v, char *out=0, int bytes=4);

extern inline int		NumMatchingBits(DWORD a, DWORD b);

int			GetIntNumBytes(int nVal);
int			GetDWordNumBytes(DWORD dwVal);

extern inline int			IsAlphabetic(char c);
extern inline int			IsAlphanumeric(char c);
extern inline int			IsDecimal(char c);
extern inline int			IsHex(char c);
extern inline int			IsSpace(char c);
extern inline int			IsPrint(char c);
extern inline int			IsUpper(char c);
extern inline int			IsLower(char c);
extern inline bool			IsIdentifierChar(char c);

extern inline int			ToUpper(int c);
extern inline int			ToLower(int c);

bool		IsStringHex(const char *szStr, int nLimit=0);
bool		IsStringInt(const char *szStr);
bool		IsStringFloat(const char *szStr);

char	*	StringToUpper(char *szStr, int nLimit=0);
char	*	StringToLower(char *szStr, int nLimit=0);
int			StringToInt(const char *szStr);
int			StringToInt(const char *szStr, char *&pStr);
int			StringToHex(const char *szStr);
int			StringToHex(const char *szStr, char *&pStr);
double		StringToFloat(const char *szStr);
double		StringToFloat(const char *szStr, char *&pStr);

void	ReverseString(char *begin, char *end);

int				strchrcount(const char *szStr, int c);
template<typename T> T strrnthchr(T szStr, int c, int n);
char		*	szncpy(char *out, const char *src, size_t size);
char		*	trim(char *szStr, const char * szChars = " \t\r\n");
char		*	ltrim(char *szStr, const char * szChars = " \t\r\n");
const char	*	ltrim(const char *szStr, const char * szChars = " \t\r\n");
char		*	rtrim(char *szStr, const char * szChars = " \t\r\n");

// std::stream stuff
void			Alert(const char *szStr);
void			Print(const char *szStr);
#define			Printf(a, ...) printf(">> "a"\n", __VA_ARGS__)
void			Output(const char *szStr);
void			Error(const char *szStr);
void			Trace(const char *szStr);
void			Exception(const char *szStr);

enum eConversion
{
	CASE_CONVERT_NONE,
	CASE_CONVERT_UPPER,
	CASE_CONVERT_LOWER,
};
enum eTypeMatchRestriction
{
	MATCH_RESTRICTION_NONE,
	MATCH_RESTRICTION_BASIC,
	MATCH_RESTRICTION_STRICT,
};

enum eCurrentDir
{
	CURRENT_DIR_LOCAL,
	CURRENT_DIR_INSTALL,
	CURRENT_DIR_DEFINITIONS,
	CURRENT_DIR_PROJECT,
};

extern class CSCRambl SCRambl;

class CSCRambl
{
public:
	size_t					m_nMemorySize;
	size_t					m_nMemoryUsed;
	char					m_szDir[MAX_PATH];
	char					m_szProjectDir[MAX_PATH];

	class CConfig
	{
	public:
		class CInstalls
		{
			int m_nNumInstalls;
			int m_nCurrentInstall;
			
			class CGameInstall
			{
			public:
				char m_cID;			// 'S'an An, 'M'iami, 'L'iberty, 'V'igilante Justice, etc.
				char m_szDefinitionPath[MAX_PATH];
				char m_szInstallPath[MAX_PATH];
				char m_szDatFilePath[MAX_PATH];
				char m_szIdeFilePath[MAX_PATH];
				char m_szPedFilePath[MAX_PATH];
				char m_szVehFilePath[MAX_PATH];
				bool m_bHavePedFilePath;
				bool m_bHaveVehFilePath;

				CGameInstall(char cID)
				{
					m_cID = cID;
					m_bHavePedFilePath = false;
					m_bHaveVehFilePath = false;
				}
			} * m_pGameInstalls[50];

		public:
			CInstalls()
			{
				m_nNumInstalls = 0;
				m_nCurrentInstall = 0;
				for(int i=0; i<sizeof(m_pGameInstalls); i++)
					m_pGameInstalls[i] = nullptr;
			}
			void AddGameInstall(CGameInstall * pGameInstall)
			{
				for(int i=0; i<m_nNumInstalls; i++)
				{
					if(pGameInstall->m_cID == m_pGameInstalls[i]->m_cID)
						THROW("Duplicate game install ID '%c'", pGameInstall->m_cID);
				}
				m_pGameInstalls[m_nNumInstalls++] = pGameInstall;
			}
			CGameInstall * GetGameInstall()
			{
				return m_pGameInstalls[m_nCurrentInstall];
			}
			void ReadConfig(const char * szPath)
			{
				CGameInstall * pGameInstall;
				const char * pStr = g_szTempString;
				char szTempPath[MAX_PATH];
				std::string sIniPath = std::string(SCRambl.m_szDir) + "\\" + std::string(szPath);
				const char * szIniPath = sIniPath.c_str();
				GetPrivateProfileString("APPS", NULL, NULL, g_szTempString, sizeof(g_szTempString), szIniPath);

				while(*pStr)
				{
					GetPrivateProfileString(pStr, "ID", NULL, szTempPath, 2, szIniPath);
					if(*szTempPath)
					{
						pGameInstall = new CGameInstall(*szTempPath);
						GetPrivateProfileString(pStr, "InstallPath", NULL, pGameInstall->m_szInstallPath, sizeof(pGameInstall->m_szInstallPath), szIniPath);
						GetPrivateProfileString(pStr, "DefinitionPath", NULL, pGameInstall->m_szDefinitionPath, sizeof(pGameInstall->m_szDefinitionPath), szIniPath);

						if(*pGameInstall->m_szInstallPath && *pGameInstall->m_szDefinitionPath)
						{
							// Get .dat path
							GetPrivateProfileString(pStr, "DatFile", "data\\gta.dat", szTempPath, sizeof(szTempPath), szIniPath);
							strcpy(pGameInstall->m_szDatFilePath, pGameInstall->m_szInstallPath);
							strcat(pGameInstall->m_szDatFilePath, "\\");
							strcat(pGameInstall->m_szDatFilePath, szTempPath);
				
							// Get .ide path
							GetPrivateProfileString(pStr, "IdeFile", "data\\default.ide", szTempPath, sizeof(szTempPath), szIniPath);
							strcpy(pGameInstall->m_szIdeFilePath, pGameInstall->m_szInstallPath);
							strcat(pGameInstall->m_szIdeFilePath, "\\");
							strcat(pGameInstall->m_szIdeFilePath, szTempPath);
				
							// Get peds.ide path
							GetPrivateProfileString(pStr, "PedsFile", "", szTempPath, sizeof(szTempPath), szIniPath);
							if(*szTempPath)
							{
								pGameInstall->m_bHavePedFilePath = true;
								strcpy(pGameInstall->m_szPedFilePath, pGameInstall->m_szInstallPath);
								strcat(pGameInstall->m_szPedFilePath, "\\");
								strcat(pGameInstall->m_szPedFilePath, szTempPath);
							}

							// Get vehicles.ide path
							GetPrivateProfileString(pStr, "VehiclesFile", "", szTempPath, sizeof(szTempPath), szIniPath);
							if(*szTempPath)
							{
								pGameInstall->m_bHaveVehFilePath = true;
								strcpy(pGameInstall->m_szVehFilePath, pGameInstall->m_szInstallPath);
								strcat(pGameInstall->m_szVehFilePath, "\\");
								strcat(pGameInstall->m_szVehFilePath, szTempPath);
							}

							AddGameInstall(pGameInstall);
						}
						else delete pGameInstall;
					}
					
					pStr = strchr(pStr, '\0') + 1;
				}
			}
		} Installs;
		class CParser
		{
			eConversion				m_eCaseConversion;
			eTypeMatchRestriction	m_eTypeMatchRestriction;
			bool					m_bUseOneBasedIndex;
			bool					m_bCreateListFile;
			
		public:
			inline bool						UseOneBasedIndex()					{return m_bUseOneBasedIndex;}
			inline bool						CreateListFile()					{return m_bCreateListFile;}
			inline eConversion				GetCaseConversion()					{return m_eCaseConversion;}
			inline eTypeMatchRestriction	GetTypeMatchRestriction()			{return m_eTypeMatchRestriction;}

			void ReadConfig(const char * szPath)
			{
				char szTempString[64];
				std::string sIniPath = std::string(SCRambl.m_szDir) + "\\" + std::string(szPath);
				const char * szIniPath = sIniPath.c_str();
				GetPrivateProfileString("PREPARSER", "CaseConversion", "UPPER", szTempString, sizeof(szTempString), szIniPath);
				strtok(szTempString, " \t;");

				if(!stricmp("LOWER", szTempString))
					m_eCaseConversion = CASE_CONVERT_LOWER;
				else if(!stricmp("UPPER", szTempString))
					m_eCaseConversion = CASE_CONVERT_UPPER;
				else
					m_eCaseConversion = CASE_CONVERT_NONE;

				GetPrivateProfileString("PREPARSER", "TypeMatchRestriction", "NONE", szTempString, sizeof(szTempString), szIniPath);
				strtok(szTempString, " \t;");

				if(!stricmp("BASIC", szTempString))
					m_eTypeMatchRestriction = MATCH_RESTRICTION_BASIC;
				else if(!stricmp("STRICT", szTempString))
					m_eTypeMatchRestriction = MATCH_RESTRICTION_STRICT;
				else
					m_eTypeMatchRestriction = MATCH_RESTRICTION_NONE;

				m_bUseOneBasedIndex = GetPrivateProfileInt("PREPARSER", "OneBasedIndex", 1, szIniPath) ? true : false;
				m_bCreateListFile = GetPrivateProfileInt("PREPARSER", "CreateListFile", 1, szIniPath) ? true : false;
			}
		} Parser;
		class COptimisation
		{
			enum eOptimisation
			{
				OPTIMISE_MIN,
				OPTIMISE_MED,
				OPTIMISE_MAX,
				OPTIMISE_CUSTOM,
			} m_eOptimiseLevel;
			bool m_bSkipUselessIFs;
			bool m_bSkipSelfAssignment;
			bool m_bSkipUselessJumps;
			int m_nAsmMultipassLevel;

		public:
			inline bool				SkipUselessIFs()					{return m_bSkipUselessIFs;}
			inline bool				SkipSelfAssignment()				{return m_bSkipSelfAssignment;}
			inline int				GetAsmMultipassLevel()				{return m_nAsmMultipassLevel;}
			void					ReadConfig(const char * szPath)
			{
				char szTempString[64];
				std::string sIniPath = std::string(SCRambl.m_szDir) + "\\" + std::string(szPath);
				const char * szIniPath = sIniPath.c_str();
				GetPrivateProfileString("OPTIMISATION", "OptimisationLevel", "CUSTOM", szTempString, sizeof(szTempString), szIniPath);

				if(stricmp(szTempString, "MAX"))
				{
					if(stricmp(szTempString, "MED"))
					{
						if(stricmp(szTempString, "MIN"))
						{
							ChangeOptimisationLevel(OPTIMISE_CUSTOM);
							m_nAsmMultipassLevel = GetPrivateProfileInt("OPTIMISATION", "AssemblyPassLevel", 0, szIniPath);
							m_bSkipUselessIFs = GetPrivateProfileInt("OPTIMISATION", "SkipUselessIFs", 0, szIniPath) ? true : false;
							m_bSkipSelfAssignment = GetPrivateProfileInt("OPTIMISATION", "SkipSelfAssignment", 0, szIniPath) ? true : false;
							m_bSkipUselessJumps = GetPrivateProfileInt("OPTIMISATION", "SkipUselessJumps", 0, szIniPath) ? true : false;
						}
						else ChangeOptimisationLevel(OPTIMISE_MIN);
					}
					else ChangeOptimisationLevel(OPTIMISE_MED);
				}
				else ChangeOptimisationLevel(OPTIMISE_MAX);
			}
			void					ChangeOptimisationLevel(eOptimisation eLvl)
			{
				switch(eLvl)
				{
				default:
					// Use MIN defaults for custom mode
					eLvl = OPTIMISE_CUSTOM;
				case OPTIMISE_MIN:
					m_eOptimiseLevel = eLvl;
					m_nAsmMultipassLevel = 0;
					m_bSkipUselessIFs = false;
					m_bSkipSelfAssignment = true;
					m_bSkipUselessJumps = false;
					break;
				case OPTIMISE_MED:
					m_eOptimiseLevel = OPTIMISE_MED;
					m_nAsmMultipassLevel = 1;
					m_bSkipUselessIFs = true;
					m_bSkipSelfAssignment = true;
					m_bSkipUselessJumps = true;
					break;
				case OPTIMISE_MAX:
					m_eOptimiseLevel = OPTIMISE_MAX;
					m_nAsmMultipassLevel = 2;
					m_bSkipUselessIFs = true;
					m_bSkipSelfAssignment = true;
					m_bSkipUselessJumps = true;
					break;
				};
			}
		} Optimisation;
		class CPerformance
		{
			size_t m_nInputBufferSize;
			size_t m_nMaxMemory;

		public:
			inline int GetInputBufferSize()								{return m_nInputBufferSize;}
			inline int GetMaxMemory()									{return m_nMaxMemory;}

			void ReadConfig(const char * szPath)
			{
				std::string sIniPath = std::string(SCRambl.m_szDir) + "\\" + std::string(szPath);
				const char * szIniPath = sIniPath.c_str();
				m_nInputBufferSize = GetPrivateProfileInt("PERFORMANCE", "InputBufferSize", 1024, szIniPath);
				m_nMaxMemory = GetPrivateProfileInt("PERFORMANCE", "MaxMemory", 204800, szIniPath);
			}
		} Performance;
	} Config;

	CSCRambl()
	{
		*m_szProjectDir = '\0';

		// Identify the version
		g_dwVersion = (VERSION_MAJOR << 24) | (VERSION_MINOR << 16) | (VERSION_REVISION << 8) | VERSION_BUILD;

		// Save the current directory
		GetWorkingDir(m_szDir, sizeof(m_szDir));

		try
		{
			// Load config.ini
			LoadConfig("config.ini");

			m_nMemorySize = Config.Performance.GetMaxMemory() / 16;

			if(!IsGameInfoLoaded())
				Print("WARNING: No application data loaded - check config.ini");

			// Load SCR definitions
			if(ChangeCurrentDirectory(CURRENT_DIR_DEFINITIONS))
			{
				LoadDefinitions();
				ResetCurrentDirectory();
			}
			
			// Load application data
			if(IsGameInfoLoaded())
				LoadGameFiles();
		}
		catch(const char *e)
		{
			Error(e);
			Exception("Failed to load one or more files");
		}
	}

	inline bool IsGameInfoLoaded()
	{
		return Config.Installs.GetGameInstall() != nullptr;
	}

	inline void SetProjectDirectory(const char * szDir)
	{
		szncpy(m_szProjectDir, szDir, sizeof(m_szProjectDir));
	}
	inline const char * GetProjectDirectory()
	{
		return *m_szProjectDir ? m_szProjectDir : nullptr;
	}
	inline bool ChangeCurrentDirectory(eCurrentDir eDir)
	{
		switch(eDir)
		{
		case CURRENT_DIR_LOCAL:
			return chdir(m_szDir) != -1;
		case CURRENT_DIR_INSTALL:
			return IsGameInfoLoaded() ? chdir(Config.Installs.GetGameInstall()->m_szInstallPath) != -1 : false;
		case CURRENT_DIR_DEFINITIONS:
			return IsGameInfoLoaded() ? chdir(Config.Installs.GetGameInstall()->m_szDefinitionPath) != -1 : false;
		case CURRENT_DIR_PROJECT:
			return GetProjectDirectory() ? chdir(GetProjectDirectory()) != -1 : false;
		}
		return false;
	}
	inline bool ResetCurrentDirectory()
	{
		return ChangeCurrentDirectory(CURRENT_DIR_LOCAL);
	}

	void LoadConfig(const char * szPath)
	{
		// Read config through modules.. fancy..
		Config.Parser.ReadConfig(szPath);
		Config.Performance.ReadConfig(szPath);
		Config.Optimisation.ReadConfig(szPath);
		Config.Installs.ReadConfig(szPath);
	}

	static char * ReadConfigLine(FILE *hFile)
	{
		char * str;
		while(str = fgets(g_szTempString, 512, hFile))
		{
			while(*str && IsSpace(*str)) str++;
			switch(*str)
			{
			case '\0':
			case ';':
			case '\n':
				continue;
			}
			return strtok(str, "\n");
		}
		return false;
	}

	static void LoadDefinitions();
	static void LoadGameFiles();
};